Skip to main content
Glama

CJ-MCP

by PoivronMax
增量引擎调用流程总结.md18.6 kB
# 增量引擎调用流程总结:从前端到后端 > 本文档总结增量引擎从前端开始一步一步往后调用的完整流程,涵盖创建阶段、首次计算阶段、状态变更阶段和更新阶段。 **基于**: Cangjie/KoalaRuntime 实现 **核心文件**: `runtime/src/core/StateManagerImpl.cj`, `runtime/src/core/ScopeImpl.cj`, `runtime/src/core/memo.cj` --- ## 目录 1. [整体调用流程概览](#1-整体调用流程概览) 2. [阶段一:前端入口 - memoRoot 创建](#2-阶段一前端入口---memoroot-创建) 3. [阶段二:首次计算 - getValue 触发](#3-阶段二首次计算---getvalue-触发) 4. [阶段三:子作用域创建 - memo/NodeAttach](#4-阶段三子作用域创建---memonodeattach) 5. [阶段四:状态变更与失效传播](#5-阶段四状态变更与失效传播) 6. [阶段五:增量更新执行](#6-阶段五增量更新执行) 7. [关键机制说明](#7-关键机制说明) --- ## 1. 整体调用流程概览 ``` 前端入口 ↓ memoRoot(rootNode, update) ↓ updatableNode → 创建根 Scope(冻结状态) ↓ 首次访问 rootScope.getValue() ↓ 执行 update(冻结窗口内) ├─ getMemoScope → 创建子作用域 ├─ param() → 包装参数为 State ├─ isUnchanged() → 判断是否需要重算 └─ recache() → 计算并缓存 ↓ 状态变更(State.setValue) ↓ updateSnapshot() → 快照推进 ↓ 失效传播(invalidate) ↓ 增量更新(仅重算受影响部分) ``` --- ## 2. 阶段一:前端入口 - memoRoot 创建 ### 2.1 调用链 ``` 用户代码: memoRoot(rootNode, update) ↓ memoRoot(node, update) ↓ GlobalStateManager.instance() ↓ StateManagerImpl.updatableNode(node, lambda1, None) ↓ ScopeImpl.init(None, 2, lambda2, lambda3, None) ↓ 返回 ComputableState<Node>(根作用域,尚未计算) ``` ### 2.2 详细步骤 #### 步骤 1: memoRoot 被调用 ```cj public func memoRoot<Node>(node: Node, update: (StateManagerImpl) -> Unit): ComputableState<Node> where Node <: IncrementalNode { let manager = GlobalStateManager.instance() manager.updatableNode(node, { => manager.runWithFrozen(update)}, None) } ``` **执行内容**: 1. 获取全局状态管理器:`GlobalStateManager.instance()` 2. **创建 lambda1**: `{ => manager.runWithFrozen(update) }` - 此时 lambda 只是被创建,**尚未执行** - `update` 参数被捕获在闭包中 3. 调用 `updatableNode(node, lambda1, None)` #### 步骤 2: updatableNode 创建根作用域 ```cj public func updatableNode<Node>(node: Node, update: () -> Unit, cleanup: ?() -> Unit): ComputableState<Node> where Node <: IncrementalNode { let scope = ScopeImpl<Node>( None, 2, { => update(); node }, // lambda2 {_ => cleanup?()}, // lambda3 None ) scope.manager = this scope.nodeAttached = node scope.nodeRef = node scope.param(0, {=> dumpHierarchyOf(scope)}, ...) // lambda4 scope.param(1, {=> dumpHierarchyOf(node)}, ...) // lambda5 return scope } ``` **执行内容**: 1. **创建 lambda2**: `{ => update(); node }`(保存为 `myCompute`,不执行) 2. **创建 lambda3**: `{_ => cleanup?()}`(保存为 `myCleanup`,不执行) 3. **创建根作用域**: `ScopeImpl.init(None, 2, lambda2, lambda3, None)` - `recomputeNeeded = true`(默认值,首次需要计算) 4. **设置作用域属性**: - `scope.manager = this` - `scope.nodeAttached = node` - `scope.nodeRef = node` 5. **创建参数状态**: - `scope.param(0, lambda4, ...)`(lambda4 保存,不执行) - `scope.param(1, lambda5, ...)`(lambda5 保存,不执行) 6. **返回根作用域** **关键点**: - 所有 lambda 在此阶段只是被创建和保存,**均未执行** - 根作用域处于"待计算"状态 --- ## 3. 阶段二:首次计算 - getValue 触发 ### 3.1 调用链 ``` 用户代码: rootScope.getValue() 或访问 .value ↓ ScopeImpl.getValue() ↓ ScopeImpl.isUnchanged() ├─ isRecomputeNeeded() → true ├─ 保存当前作用域: scopeInternal = manager.currentScope ├─ 切换作用域: manager.currentScope = this └─ 返回 false(需要重算) ↓ ScopeImpl.getValue() 继续 ├─ 获取 lambda2: myCompute.getOrThrow() └─ 执行 lambda2: compute() ↓ 执行 lambda1: manager.runWithFrozen(update) ├─ frozen = true(进入冻结窗口) ├─ 执行用户 update(manager) └─ frozen = old(退出冻结窗口) ↓ 返回 node ↓ ScopeImpl.recache(node) ├─ 恢复作用域: manager.currentScope = scopeInternal ├─ 更新缓存: myValue = node, recomputeNeeded = false ├─ detachChildScopes(None) ├─ parent?.increment(...) ├─ nodeAttached?.incrementalUpdateDone(...) └─ getCached() └─ 返回 node ``` ### 3.2 详细步骤 #### 步骤 1: getValue 被调用 ```cj public func getValue(): Value { if (this.isUnchanged()) { this.getCached() } else { let compute = this.myCompute.getOrThrow() this.recache(compute()) } } ``` #### 步骤 2: isUnchanged 判断(首次返回 false) ```cj public func isUnchanged(): Bool { if (this.isRecomputeNeeded()) { this.incremental = None this.nodeCount = 0 if (!this.isDisposed()) { if(let Some(manager) <- this.manager) { this.scopeInternal = manager.currentScope // 保存 manager.currentScope = this // 切换 } } return false // 需要重算 } else { // 缓存命中,跳过计算 this.parent?.increment(...) return true } } ``` **关键点**: - 切换 `manager.currentScope` 后,后续的状态访问会在此作用域下登记依赖 #### 步骤 3: 执行计算 lambda2 ```cj let compute = this.myCompute.getOrThrow() // 获取 lambda2 this.recache(compute()) // 执行 lambda2 ``` lambda2 的内容: ```cj { => update() // 这是 lambda1: { => manager.runWithFrozen(update) } node } ``` #### 步骤 4: 执行 lambda1(runWithFrozen) ```cj func runWithFrozen(runnable: (StateManagerImpl) -> Unit): Unit { let old = frozen frozen = true // 进入冻结窗口(防止状态在重组期间被修改) runnable(this) // 执行用户传入的 update(manager) frozen = old // 退出冻结窗口 } ``` **执行内容**: 1. 设置 `frozen = true`(冻结状态修改) 2. **执行用户 update 函数**(在冻结窗口内) - 用户代码可能调用 `memo`、`NodeAttach` 等 - 这些会创建子作用域并可能触发计算 3. 恢复 `frozen = old` #### 步骤 5: recache 更新缓存 ```cj public func recache(newValue: Value): Value { // 恢复作用域 if (!this.isDisposed()) { if(let Some(manager) <- this.manager) { manager.currentScope = this.scopeInternal } } // 更新缓存值 let oldValue = this.myValue this.myValue = newValue this.myModified = this.myComputed && !equalValues(newValue, oldValue.getOrThrow()) this.myComputed = true this.recomputeNeeded = false // 维护子树 this.detachChildScopes(None) this.parent?.increment(...) this.nodeAttached?.incrementalUpdateDone(this.parent?.nodeRef ?? None) return this.getCached() } ``` --- ## 4. 阶段三:子作用域创建 - memo/NodeAttach ### 4.1 在用户 update 中创建子作用域 当用户的 `update` 函数执行时,可能调用 `memo` 或 `NodeAttach`,这些会创建子作用域。 #### 示例:memo2 调用流程 ```cj func memo2<P1, P2, Value>(p1: P1, p2: P2, compute: (__memo_key: Hashscopeid, manager: StateManagerImpl, s1: State<P1>, s2: State<P2>) -> Value): Value { let scope = manager.getMemoScope<Value>(_new_id, 2, None, None, None, false, None) let s1 = scope.param(0, p1) let s2 = scope.param(1, p2) if (scope.isUnchanged()) { scope.getCached() } else { scope.recache(compute(s1, s2)) } } ``` ### 4.2 子作用域创建步骤 #### 步骤 1: getMemoScope 获取/创建子作用域 ```cj public func getMemoScope<Value>(id: Hashscopeid, paramCount: Int64): MemoScope<Value> { getMemoScope(id, paramCount, None, None, None, false, None) } ``` **执行内容**: 1. 在父作用域中按 `id` 扫描兄弟链,查找可复用的子作用域 2. 如果未找到,创建新的 `ScopeImpl` 3. 将新作用域接入链表/树结构 #### 步骤 2: param 包装参数为 State ```cj public func param<Value>(index: Int64, value: Value, name: ?String, contextLocal: Bool): State<Value> { let params = this.params.getOrThrow() let manager = this.manager.getOrThrow() if (let Some(param) <- params[index]) { // 复用已有参数状态 let state = (param as ParameterImpl<Value>).getOrThrow() state.setValue(value) return state } else { // 创建新的参数状态 let state = ParameterImpl<Value>(value, name, manager) params[index] = state return state } } ``` **关键点**: - 参数被包装为 `ParameterImpl`(一种 State) - 访问参数时会自动登记依赖 #### 步骤 3: isUnchanged 判断 ```cj if (scope.isUnchanged()) { scope.getCached() // 缓存命中,跳过计算 } else { scope.recache(compute(s1, s2)) // 需要重算 } ``` **首次调用**: `isUnchanged()` 返回 `false`,进入计算分支 #### 步骤 4: 执行 compute 并 recache ```cj scope.recache(compute(s1, s2)) ``` **执行内容**: 1. 执行用户传入的 `compute` 函数 2. 在 `compute` 中访问 `s1`、`s2` 时,会触发依赖登记 3. `recache` 缓存计算结果并维护子树 ### 4.3 依赖登记机制 #### State 访问时登记依赖 ```cj // StateImpl.getValue() private func onAccess(): Unit { if(let Some(dep) <- this.manager?.getDependency()) { this.dependencies?.register(dep) // 登记当前作用域为依赖 } } ``` #### 参数访问时登记依赖 ```cj // ParameterImpl.getValue() public func getValue(): Value { if(let Some(dep) <- this.manager?.getDependency()) { this.dependencies?.register(dep) } return this.value } ``` **依赖关系建立**: - State/Parameter → 记录依赖它的 Scope - Scope → 通过 `manager.currentScope` 获取当前作用域 - 形成依赖链:State → Scope(依赖关系) --- ## 5. 阶段四:状态变更与失效传播 ### 5.1 状态变更入口 用户代码设置状态值: ```cj counter.setValue(10) ``` ### 5.2 状态变更流程 #### 步骤 1: StateImpl.setValue ```cj public func setValue(value: Value) { this.current = value this.updated = false if (let Some(manager) <- this.manager) { manager.updateNeeded = true // 标记需要更新 } else { this.updateStateSnapshot() } } ``` **核心动作**: 不立即对比,只标记本轮需要更新 ### 5.3 快照推进阶段 由外部驱动(下一帧/周期): ```cj public func updateStateManager(manager!: StateManagerImpl = GlobalStateManager.instance()): Unit { manager.updateSnapshot() } ``` #### updateSnapshot 执行流程 ```cj public func updateSnapshot(): UInt64 { this.checkForStateComputing() var modified: UInt64 = 0 if (this.updateNeeded) { // 阶段 1: 更新所有 State 的快照 if (!this.createdStates.isEmpty()) { for (state in this.createdStates) { state.updateStateSnapshot() if (state.isModified()) { modified++ } } } // 阶段 2: 消费脏作用域 while (!this.dirtyScopes.isEmpty()) { let scopes = this.dirtyScopes.toArray() this.dirtyScopes.clear() for (scope in scopes) { if (scope.isModified()) { modified++ } } } this.updateNeeded = modified > 0 } return modified } ``` ### 5.4 State 快照更新与失效触发 ```cj public func updateStateSnapshot(): Unit { if (this.updated) { this.modified = false } else { this.updated = true this.modified = !equalValues(this.snapshot, this.current) if (this.modified) { this.snapshot = this.current } } // 触发依赖失效 this.dependencies?.onUpdate(this.modified) } ``` ### 5.5 失效传播机制 #### 依赖集合触发失效 ```cj // Dependencies.onUpdate() public func onUpdate(invalidate: Bool): Unit { if (invalidate) { if (let Some(dependencies) <- this.dependencies) { for (dependency in dependencies) { dependency.invalidate() // 调用每个依赖的 invalidate() } } } } ``` #### Scope 的失效逻辑 ```cj public func invalidate(): Unit { if(let Some(manager) <- this.manager) { let current = manager.currentScope var scope: ManagedScope = this while (true) { if (Some(scope) == current) { break } // 避免重组中的广泛失效 scope.recomputeNeeded = true // 标记需要重算 if (let Some(parent) <- scope.parent) { scope = parent // 向上传播 } else { // 到达顶层,加入脏作用域集合 if (let Some(deps) <- scope.dependencies) { if (!deps.empty) { manager.addDirtyScope(scope) } } break } } } } ``` **传播规则**: - 向上传播到顶层作用域 - 若遇到"当前正在计算的作用域"(`currentScope`),则停止 - 到达顶层且存在依赖时,加入 `dirtyScopes` --- ## 6. 阶段五:增量更新执行 ### 6.1 更新触发 当作用域被访问时(如 `getValue()`),会检查是否需要重算: ```cj public func getValue(): Value { if (this.isUnchanged()) { this.getCached() // 缓存命中,跳过计算 } else { let compute = this.myCompute.getOrThrow() this.recache(compute()) // 需要重算 } } ``` ### 6.2 增量更新流程 #### 场景:只有组件 A 依赖的状态变更 ``` 状态变更: counter.setValue(10) ↓ updateSnapshot() ├─ counter.updateStateSnapshot() → modified=true └─ counter.dependencies.onUpdate(true) └─ A.invalidate() ├─ A.recomputeNeeded = true └─ Root.recomputeNeeded = true ↓ 下一次渲染: Root.getValue() ├─ Root.isUnchanged() → false(进入计算) │ ├─ A.isUnchanged() → false(重算 A,读取 counter=10,recache) │ └─ B.isUnchanged() → true(跳过,使用缓存) └─ Root.recache() → 完成 ``` #### 增量跳过机制 对于未变更的子树,使用 `IncrementalNode.incrementalUpdateSkip(count)` 快速跳过: ```cj func incrementalUpdateSkip(count: UInt32) { // 快速跳过未变更的子树节点 } ``` **结果**: 仅 A 重算、B 跳过;Root 快速完成 --- ## 7. 关键机制说明 ### 7.1 冻结窗口(Frozen Window) **目的**: 防止状态在重组期间被修改 **实现**: ```cj func runWithFrozen(runnable: (StateManagerImpl) -> Unit): Unit { let old = frozen frozen = true // 进入冻结窗口 runnable(this) frozen = old // 退出冻结窗口 } ``` **效果**: 在 `update` 执行期间,状态修改被禁止 ### 7.2 作用域切换(Scope Switching) **目的**: 确保状态访问时能正确登记依赖 **实现**: ```cj // 进入计算前 this.scopeInternal = manager.currentScope // 保存 manager.currentScope = this // 切换 // 计算完成后 manager.currentScope = this.scopeInternal // 恢复 ``` **效果**: 在作用域计算期间,所有状态访问都会登记到当前作用域 ### 7.3 依赖登记(Dependency Registration) **时机**: 读时注册(Read-Time Registration) **流程**: 1. 作用域计算中访问 State/Parameter 2. State/Parameter 的 `getValue()` 被调用 3. `onAccess()` 获取当前作用域(`manager.getDependency()`) 4. 将当前作用域注册到 State/Parameter 的 `dependencies` **数据结构**: - `State.dependencies: ?Dependencies` → 存储依赖该 State 的所有 Scope - `Scope` 通过 `manager.currentScope` 获取当前作用域 ### 7.4 失效传播(Invalidation Propagation) **规则**: 1. 状态变更 → 标记 `updateNeeded = true` 2. 快照推进 → 比较 `snapshot` vs `current`,判定 `modified` 3. 失效触发 → `dependencies.onUpdate(true)` 调用每个依赖的 `invalidate()` 4. 向上传播 → Scope 的 `invalidate()` 向上传播到顶层 5. 加入脏集合 → 顶层作用域加入 `dirtyScopes` ### 7.5 增量更新(Incremental Update) **策略**: - **缓存命中**: `isUnchanged() == true` → 直接返回缓存,跳过计算 - **需要重算**: `isUnchanged() == false` → 执行 `compute()` 并 `recache()` - **节点跳过**: 未变更子树使用 `incrementalUpdateSkip()` 快速跳过 **效果**: 只重算受影响的部分,最小化更新成本 --- ## 总结 ### 完整调用流程 1. **前端入口**: `memoRoot` → 创建根作用域(冻结状态) 2. **首次计算**: `getValue()` → 执行 `update`(冻结窗口内) 3. **子作用域**: `getMemoScope` → `param` → `isUnchanged` → `recache` 4. **依赖登记**: 状态访问时自动登记依赖关系 5. **状态变更**: `setValue` → 标记 `updateNeeded` 6. **快照推进**: `updateSnapshot` → 比较并触发失效 7. **失效传播**: `invalidate` → 向上传播到顶层 8. **增量更新**: 仅重算受影响的作用域,其他部分跳过 ### 关键设计原则 1. **延迟执行**: Lambda 在创建时不执行,只在需要时才执行 2. **读时注册**: 依赖在状态访问时自动登记 3. **写时标记**: 状态变更只标记,不立即计算 4. **快照推进**: 在下一帧统一推进快照并触发失效 5. **增量重算**: 只重算受影响的部分,最大化缓存命中率 ### 性能优化点 1. **作用域复用**: 通过 `id` 和参数匹配复用作用域 2. **缓存机制**: 未变更的作用域直接返回缓存 3. **增量跳过**: 未变更子树快速跳过,不参与计算 4. **冻结保护**: 防止重组期间的状态修改,保证一致性 --- **文档版本**: v1.0 **最后更新**: 2024

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/PoivronMax/idlize-cj-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server